#include"GLSLProgram.h"

#include <fstream>
#include <cstring>
#include <cstdlib>

using namespace std;
GLSLProgram::GLSLProgram()
{
}

GLSLProgram::~GLSLProgram()
{
	if(buffer)
		delete buffer;
}
void GLSLProgram::checkInput(const char * filename)
{
  FILE *fp = fopen(filename, "r");
  if(!fp)
  {
    cout<<"Can't open - "<<filename;
    system("pause");
    exit(0);
  }

  else 
  {
    fclose(fp);
    return;
  }
}

std::string* GLSLProgram::LoadShaderToString(const char* filename)
{
  checkInput(filename);
	int size = 100000;
	char *cbuffer = new GLchar[size];
	memset(cbuffer, 0, sizeof(GLchar)*size);

	//load from file
	ifstream shader;
	shader.open(filename, ios::in);
	shader.seekg(0,ios::end);
	int length = int(shader.tellg());
	shader.seekg(0, ios::beg);		 
	shader.read(cbuffer,length);
	shader.close();
	std::string* str = new std::string(cbuffer);

	return str;
}

bool GLSLProgram::MakeProgramFromString(std::string* vertString, std::string* fragString)
{
	GLuint vertexglsl, fragmentglsl;
	//vertex
	vertexglsl = glCreateShader(GL_VERTEX_SHADER);
	GLchar const* vertC =	 vertString->c_str();
	GLint const vertSize = (GLint const)vertString->size();
	glShaderSource(vertexglsl,1, &vertC, &vertSize);
	glCompileShader(vertexglsl);
	_printlog(vertexglsl);
	cout << "\nvertexshader compiled!\n" << endl;

	//fragment
	fragmentglsl = glCreateShader(GL_FRAGMENT_SHADER);	
	GLchar const* fragC =	 fragString->c_str();
	GLint const fragSize = (GLint const )fragString->size();
	glShaderSource(fragmentglsl,1, &fragC,  &fragSize);
	glCompileShader(fragmentglsl);
	_printlog(fragmentglsl);
	cout << " fragmentshader compiled!\n" << endl;

	///clear up
	delete vertString;
	delete fragString;			 

	///////////////////////////////////////
	///verify 
	GLint vertcompiled, fragmentcompiled, linked;

	glGetShaderiv(vertexglsl,GL_COMPILE_STATUS,&vertcompiled);
	glGetShaderiv(fragmentglsl,GL_COMPILE_STATUS,&fragmentcompiled);

	if(!vertcompiled)
		return false;

	//create program
	prog = glCreateProgram();

	//Setup GS
	//glProgramParameteriEXT(prog, GL_GEOMETRY_VERTICES_OUT_EXT, 1000);
	//glProgramParameteriEXT(prog, GL_GEOMETRY_INPUT_TYPE_EXT, GL_POINTS);
	//glProgramParameteriEXT(prog, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);

	//Attach Shader
	glAttachShader(prog, vertexglsl);
	glAttachShader(prog, fragmentglsl);

	//Link program
	glLinkProgram(prog);

	//get program info
	glGetProgramiv(prog, GL_LINK_STATUS, &linked);
	_printlog(prog);

	if(!linked)
	{
		cout << "Link failed! \n"<< endl;
		return false;
	}
	return true;
}


bool GLSLProgram::MakeProgram(const char* vertexsh, const char* fragmentsh, const char* geometrysh )
{
	GLuint vertexglsl, fragmentglsl, geometryglsl;
	vertexglsl = glCreateShader(GL_VERTEX_SHADER);
	fragmentglsl = glCreateShader(GL_FRAGMENT_SHADER);
	geometryglsl = glCreateShader(GL_GEOMETRY_SHADER_EXT);

	//TODO make the length of the char adapted to the length of the shader source file
	buffer = new GLchar[100000];
	for(int i = 0; i< 100000; i++)
		buffer[i] = ' ';
	_loadshader(vertexsh, buffer);
	glShaderSource(vertexglsl,1,(GLchar const**)&buffer,&len);
	glCompileShader(vertexglsl);
	_printlog(vertexglsl);
	cout << "\nvertexshader compiled!\n" << endl;

	if (fragmentsh)
	{
		for(int i = 0; i< 100000; i++)
			buffer[i] = ' ';
		_loadshader(fragmentsh, buffer);//ftransf.glsl
		glShaderSource(fragmentglsl,1,(GLchar const**)&buffer,&len);
		glCompileShader(fragmentglsl);
		_printlog(fragmentglsl);
		cout << "fragmentshader compiled!\n" << endl;
	}

	if (geometrysh)
	{
		for(int i = 0; i< 100000; i++)
			buffer[i] = ' ';
		_loadshader(geometrysh, buffer);
		glShaderSource(geometryglsl,1,(GLchar const**)&buffer,&len);
		glCompileShader(geometryglsl);
		_printlog(geometryglsl);
		cout << "geometry shader compiled!\n" << endl;
	}

	cout <<"-------------"<<endl;

	delete[] buffer;
	GLint vertcompiled, fragmentcompiled, linked;
	glGetShaderiv(vertexglsl,GL_COMPILE_STATUS,&vertcompiled);
	glGetShaderiv(fragmentglsl,GL_COMPILE_STATUS,&fragmentcompiled);

	if(!vertcompiled)
		return false;

	//create program
	prog = glCreateProgram();

	//Setup GS
	//glProgramParameteriEXT(prog, GL_GEOMETRY_VERTICES_OUT_EXT, 1000);
	//glProgramParameteriEXT(prog, GL_GEOMETRY_INPUT_TYPE_EXT, GL_POINTS);
	//glProgramParameteriEXT(prog, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);

	//Attach Shader
	glAttachShader(prog, vertexglsl);
	if(fragmentsh)
	glAttachShader(prog, fragmentglsl);
	if(geometrysh)
	glAttachShader(prog, geometryglsl);


	//Link program
	glLinkProgram(prog);

	//get program info
	glGetProgramiv(prog, GL_LINK_STATUS, &linked);
	_printlog(prog);

	if(!linked)
	{
		cout << "Link failed! \n"<< endl;
		return false;
	}


	return true;

}

void GLSLProgram::BindTexture(GLenum unit, GLenum texturetype, GLuint textureid, const char* nameinshader)
{
	glActiveTexture(unit);
	glBindTexture(texturetype, textureid);
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	//printf("loc: %d, texID:%d, name:%s\n", loc, textureid, nameinshader);
	glUniform1i(loc,unit-GL_TEXTURE0);	

}

void GLSLProgram::setUniform1f(const char* nameinshader, float value)
{
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	glUniform1f(loc, value);

}

void GLSLProgram::setUniform3f(const char* nameinshader, float* value)
{
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	glUniform3f(loc, value[0], value[1], value[2]);

}

void GLSLProgram::setUniformMatf(const char* nameinshader, float* value)
{
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	glUniformMatrix4fv(loc, 1, 0, value);

}

void GLSLProgram::setUniformMat3f(const char* nameinshader, float* value)
{
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	glUniformMatrix3fv(loc, 1, 0, value);

}

void GLSLProgram::setUniform1uint(const char* nameinshader, unsigned int value)
{
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	glUniform1uiEXT(loc, value);
}

void GLSLProgram::setUniform3uint(const char* nameinshader, unsigned int x,unsigned int y, unsigned int z)
{
	GLuint loc = glGetUniformLocation(prog, nameinshader);
	glUniform3uiEXT(loc, x, y, z);
}


void GLSLProgram::BeginProgram()
{
	glUseProgram(prog);
}

void GLSLProgram::EndProgram()
{
	glUseProgram(0);
}

//==========private member===========

void GLSLProgram::_loadshader(const char* filename, char* buffer)
{
  checkInput(filename);


	ifstream shader;
	shader.open(filename, ios::in);
	shader.seekg(0,ios::end);
	len = int(shader.tellg());
	shader.seekg(0, ios::beg);

	shader.read(buffer, len);
	shader.close();

}

void GLSLProgram::_printlog(GLuint obj)
{
	int infologLength = 0;
	int maxLength;

	if(glIsShader(obj))
		glGetShaderiv(obj,GL_INFO_LOG_LENGTH,&maxLength);
	else
		glGetProgramiv(obj,GL_INFO_LOG_LENGTH,&maxLength);

	char *infoLog = new char[maxLength];

	if (glIsShader(obj))
		glGetShaderInfoLog(obj, maxLength, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, maxLength, &infologLength, infoLog);

	if (infologLength > 0)
		printf("%s\n",infoLog);
}
